﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Diagnostics.Contracts;
using SimpleData.Infrastructure.Plugins;
using SimpleData.ObjectModel;

namespace SimpleData.Infrastructure.Provider
{
	/// <summary>
	/// 
	/// </summary>
	/// <typeparam name="TName"></typeparam>
	/// <typeparam name="TPlugin"></typeparam>
	public abstract class ProviderBase<TName, TPlugin> : IProvider<TName, TPlugin>
		where TName : IDataObjectName
		where TPlugin : class
	{

		/// <summary>
		/// 
		/// </summary>
		protected ProviderBase()
			: this( null )
		{ }

		/// <summary>
		/// 
		/// </summary>
		/// <param name="container"></param>
		protected ProviderBase( Plug container )
		{
			this.Container = container;
			this.AssemblyProbe = new List<AssemblyLoaderProbe>();
		}


		/// <summary>
		/// Obtém ou define o container com os objeto que podem ser plugados externamentes
		/// </summary>
		public Plug Container
		{
			get;
			private set;
		}


		/// <summary>
		/// Obtém a lista de assemblies onde o builder da rule será procurado
		/// </summary>
		public List<AssemblyLoaderProbe> AssemblyProbe
		{
			get;
			private set;
		}

		#region IProvider<TSettings,TData> Members

		/// <summary>
		/// 
		/// </summary>
		/// <param name="data"></param>
		/// <returns></returns>
		public virtual TPlugin Create( TName name )
		{
			//Contract.Requires<ArgumentNullException>( name != null, "name" );
			if ( name != null )
			{
				TPlugin obj = this.LoadFromProbeAssemblies( name ) ?? this.LoadFromPluginContainer( name );
				return obj;
			}
			else
				return default( TPlugin );
		}

		#endregion

		/// <summary>
		/// Procura pelo builder nos assemblies
		/// </summary>
		/// <param name="rule"></param>
		/// <returns></returns>
		private TPlugin LoadFromProbeAssemblies( IDataObjectName name )
		{
			// Procura no assembly atual se existe 
			string typeName = name.ToDotNetNamespace();

			int index = 0;
			Type providedType = null;

			// Procura em cada assembly, 
			// tenta carregar o objeto que foi plugado pelo assembly
			while ( index < this.AssemblyProbe.Count
				&& !this.AssemblyProbe[ index++ ].TryGetType( typeName, out providedType ) )
				;

			// Se encontrou o tipo do builder
			if ( providedType != null )
				return Activator
					.CreateInstance( providedType )
					.Throws()
					.IfCastFails<TPlugin>();
			else
				return null;
		}

		/// <summary>
		/// Tenta carregar do container IoC
		/// </summary>
		/// <param name="rule"></param>
		/// <returns></returns>
		private TPlugin LoadFromPluginContainer( IDataObjectName name )
		{
			if ( this.Container != null )
				return ( TPlugin )this.Container.Create( name, typeof( TPlugin ) );
			else
				return null;
		}

		#region [ class AssemblyLoaderProbe ]

		/// <summary>
		/// Define os assemblies em que o objeto vai procurar
		/// </summary>
		public class AssemblyLoaderProbe
		{
			/// <summary>
			/// 
			/// </summary>
			/// <param name="asm"></param>
			public AssemblyLoaderProbe( Assembly asm )
				: this( asm, null )
			{ }

			/// <summary>
			/// 
			/// </summary>
			/// <param name="asm"></param>
			/// <param name="typeSufix"></param>
			public AssemblyLoaderProbe( Assembly asm, string typeSufix )
			{
				Contract.Requires<ArgumentNullException>( asm != null );

				this.TypeSufix = typeSufix;
				this.Asm = asm;
			}

			/// <summary>
			/// Obtém ou define o sufixo para determinar tipo que será carregada
			/// </summary>
			public string TypeSufix
			{ get; private set; }

			/// <summary>
			/// Obtem ou define o assembly onde será procurado o tipo
			/// </summary>
			public Assembly Asm
			{ get; private set; }

			/// <summary>
			/// Tenta carregar o tipo
			/// </summary>
			/// <param name="typeName"></param>
			/// <param name="type"></param>
			/// <returns></returns>
			public bool TryGetType( string typeName, out Type type )
			{
				string fullName;

				if ( String.IsNullOrEmpty( this.TypeSufix ) )
					fullName = typeName;
				else
					fullName = String.Concat( typeName, this.TypeSufix );

				type = this.Asm.GetType( fullName );
				return type != null;
					
					//type = null;
				//if ( !String.IsNullOrEmpty( this.TypeSufix ) )
				//    type = this.Asm.GetType( String.Concat( typeName, ".", this.TypeSufix ), false, true );

				//if ( type == null )
				//    type = this.Asm.GetType( typeName, false, true );

				//return type != null;
			}
		}

		#endregion
	}
}
